# pragma once

#include <deque>
#include <set>
#include "ArduinoJson.h"
#include "Ticker.h"
# include "../ganix/spiffs.hpp"
# include "global.hpp"
# include "actions.hpp"
# include "connection_manager.hpp"

class ConfigLine {
    private:
    bool is_active;

    public:
    String worker_name;
    String pin_name;
    String pin_action;
    String action_target;
    String pin_function;
    String args;

    ConfigLine() {
        
    }

    ConfigLine(String line): ConfigLine() {
        deque<String> obj = string_split(line, ' ');
        worker_name = obj[0];
        pin_name = obj[1];
        pin_action = obj[2];
        action_target = obj[3];
        pin_function = obj[4];
        args = obj[5];

        is_active = WORKER_NAME == worker_name;
    }

    bool valid() {
        if (!is_worker_valid(worker_name) || !is_worker_valid(action_target)) {
            return false;
        }
        if (!rpc_handler.valid_action(pin_function)) {
            return false;
        }
        if (args.length() > 50) {
            return false;
        }
        return true;
    }

    String dump_string() {
        deque<String> ret;
        ret.push_back(worker_name);
        ret.push_back(pin_name);
        ret.push_back(pin_action);
        ret.push_back(action_target);
        ret.push_back(pin_function);
        ret.push_back(args);
        return string_join(ret, ' ');
    }

    bool accept(String _pin_name, String _pin_action) {
        Serial.println("accepting... " + dump_string());
        if (is_active && pin_name == _pin_name && pin_action == _pin_action) {
            call_pin_function(action_target, pin_function, args);
            Serial.println("accept success!");
            return true;
        } else {
            /*
            Serial.printf("failed. (%s):(%s) (%s):(%s)\n",
             pin_name.c_str(), _pin_name.c_str(),
             pin_action.c_str(), _pin_action.c_str());
             */
            return false;
        }
    }
};

static const String CONF_FILE_PATH = "/home/light.conf";
static const String VERSION_FILE_PATH = "/home/light.version";
class IoActionConfig {
    public:
    int version;
    std::deque<ConfigLine> configs, tmp_configs;
    std::set<int> accepted;
    
    IoActionConfig(): version(-1) {}

    void load_config_from_file() {
        String config_str = ganix::fs::load(CONF_FILE_PATH);
        String version_str = ganix::fs::load(VERSION_FILE_PATH);
        load_config_from_str(config_str, version_str.toInt());
    }

    void add_config_line(ConfigLine line) {
        if (line.valid()) {
            configs.push_back(line);
        }
    }

    void load_config_from_str(String str, int ver) {
        version = ver;
        configs.clear();
        for (auto line : string_split(str, '\n')) {
            add_config_line(ConfigLine(line));
        }
    }

    void load_config_from_json_array(JsonArray array, int ver) {
        version = ver;
        configs.clear();
        for (JsonVariant line : array) {
            add_config_line(ConfigLine(line.as<String>()));
        }
    }

    void update_from_neibor(int ver, int pkg_count, int pkg_id, const JsonArray &array) {
        if (version >= ver || accepted.find(pkg_id) != accepted.end()) {
            return ;
        }
        Serial.printf("updating ver(%d->%d), pkg[%d] sz(%d)\n", version, ver, pkg_id, pkg_count);
        for (JsonVariant line : array) {
            ConfigLine cfg_line(line.as<String>());
            if (cfg_line.valid()) {
                tmp_configs.push_back(cfg_line);
            }
        }
        accepted.insert(pkg_id);
        if (accepted.size() == pkg_count) {
            Serial.printf("all cfg updated, saving ver:%d\n", ver);
            configs = tmp_configs;
            version = ver;
            accepted.clear();
            tmp_configs.clear();
            save_config_to_file();
        }
    }

    String dump_string() {
        std::deque<String> tmp;
        tmp.push_back(String(version));
        for (ConfigLine line : configs) {
            tmp.push_back(line.dump_string());
        }
        return string_join(tmp, '\n');
    }

    void save_config_to_file() {
        ganix::fs::save(CONF_FILE_PATH, dump_string(), false);
        ganix::fs::save(VERSION_FILE_PATH, String(version), false);
    }

    void append_config(ConfigLine line) {
        add_config_line(line);
        ganix::fs::save(CONF_FILE_PATH, '\n' + line.dump_string(), true);
        ganix::fs::save(VERSION_FILE_PATH, String(++version), false);
    }

    void append_config(String str) {
        append_config(ConfigLine(str));
    }

    void accept(String pin_name, String pin_action) {
        bool accepted = false;
        for (ConfigLine line : configs) {
            accepted |= line.accept(pin_name, pin_action);
        }
        if (!accepted) {
            call_rpc_pin_none(pin_name, pin_action);
        }
    }
}io_action_config;


void trigger_input_pin(String name, int state) {
    Serial.printf("input pin action! pin:%s state:%d\n", name.c_str(), state);
    if (state == 0) {
        io_action_config.accept(name, PIN_ACTION_DOWN);
    } else {
        io_action_config.accept(name, PIN_ACTION_UP);
    }
    io_action_config.accept(name, PIN_ACTION_SW);
}

void handler_rpc_config_sync(String call_name, JsonArray args, JsonObject kwargs) {
    int version = kwargs[ARG_KEY_CONFIG_VERSION];
    int pkg_count = kwargs[ARG_KEY_CONFIG_PKG_COUNT];
    int id = kwargs[ARG_KEY_CONFIG_PKG_ID];
    io_action_config.update_from_neibor(version, pkg_count, id, args);
}

static Ticker ticker_config_sync;

void init_io_action_config() {
    const int PKG_SIZE = 5;
    static int pkg_id = 0;

    rpc_handler.regist(ACTION_CONFIG_SYNC, handler_rpc_config_sync);
    io_action_config.load_config_from_file();
    ticker_config_sync.attach_ms_scheduled(3000 + random(2000), [&pkg_id](){
        //Serial.printf("io action sync... version : %d\n", io_action_config.version);
        int n = io_action_config.configs.size();
        int pkg_count = n / PKG_SIZE + (n % PKG_SIZE != 0);
        Message message(TARGET_BROCAST, ACTION_CONFIG_SYNC);
        message.add_kv(ARG_KEY_CONFIG_VERSION, io_action_config.version);
        message.add_kv(ARG_KEY_CONFIG_PKG_COUNT, pkg_count);
        message.add_kv(ARG_KEY_CONFIG_PKG_ID, pkg_id);
        for (int j = pkg_id * 5; j < n && j < pkg_id * 5 + 5; j++) {
            message.add_arg(io_action_config.configs[j].dump_string());
        }
        conn_mgr.call_message_rpc(message);
        pkg_id = (pkg_id + 1) % pkg_count;
    });
}